/* @flow */

import config from '../config'
import Watcher from '../observer/watcher'
import { mark, measure } from '../util/perf'
import { createEmptyVNode } from '../vdom/vnode'
import { updateComponentListeners } from './events'
import { resolveSlots } from './render-helpers/resolve-slots'
import { toggleObserving } from '../observer/index'
import { pushTarget, popTarget } from '../observer/dep'

import {
  warn,
  noop,
  remove,
  handleError,
  emptyObject,
  validateProp
} from '../util/index'

// 这个变量将总是保存着当前正在渲染的实例的引用，也是当前实例components下注册的子组件的父实例(Vue也是通过这个变量实现自动侦测父级的)
export let activeInstance: any = null
// 定义 isUpdatingChildComponent，并初始化为 false
export let isUpdatingChildComponent: boolean = false

export function initLifecycle (vm: Component) {
  // 定义 options，它是 vm.$options 的引用，后面的代码使用的都是 options 常量
  const options = vm.$options

  // locate first non-abstract parent(查找第一个非抽象的父组件)
  // 定义 parent，它引用当前实例的父实例(即：activeInstance变量)
  let parent = options.parent
  // 如果当前实例有父组件，且当前实例不是抽象的（abstract：为Vue内部未暴露的选项，抽象组件一般不渲染真实DOM，如keep-alive，transition组件）
  // 这也说明：抽象实例不会被添加到父实例的$children中
  if (parent && !options.abstract) {
    // 使用 while 循环目的就是沿着父实例链逐层向上查找第一个非抽象的实例作为parent(父级)
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    // 经过上面的 while 循环后，parent 应该是一个非抽象的组件，将它作为当前实例的父级，所以将当前实例 vm 添加到父级的 $children 属性里
    parent.$children.push(vm)
  }
  // 设置当前实例的 $parent 属性，指向父级
  vm.$parent = parent
  // 设置 $root 属性，有父级就是用父级的 $root，否则 $root 指向自身
  vm.$root = parent ? parent.$root : vm
  /**
   * 上面这部分代码的作用就是：将当前实例添加到父实例的 $children 属性里，并设置当前实例的 $parent 指向父实例
   */

  // 在当前实例上添加一些属性（到此已经完成生命周期有关的初始化已经完成） 
  vm.$children = []
  vm.$refs = {}
  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

export function lifecycleMixin (Vue: Class<Component>) {
  // 1、在Vue.prototype 上添加_update方法
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = vm
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // vm.$el 的值将被 vm.__patch__ 函数的返回值重写
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }
  // 2、在Vue.prototype 上添加$forceUpdate方法
  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }
  // 3、在Vue.prototype 上添加$destroy方法
  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

/**
 * 真正实现$mount操作的函数
 * @param {组件实例} vm 
 * @param {挂载元素} el 
 * @param {透传过来的 hydrating} hydrating 
 */ 
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // 在组件实例对象上添加 $el 属性(实际指的是组件模板的根元素)，其值为挂载元素 el
  vm.$el = el
  // 渲染函数如果不存在
  if (!vm.$options.render) {
    // 将 vm.$options.render 的值设置为 createEmptyVNode 函数，此时渲染函数的作用将仅仅渲染一个空的 vnode 对象
    vm.$options.render = createEmptyVNode
    // 在非生产环境下给出警告
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  // 执行callHook 函数，触发 beforeMount 生命周期钩子，触发之后开始正直的挂载工作
  callHook(vm, 'beforeMount')

  // 定义并初始化 updateComponent 函数，用于创建 Watcher 实例时传递给 Watcher 构造函数的第二个参数
  // 其真正的作用是：把渲染函数生成的虚拟DOM渲染成真正的DOM
  let updateComponent
  // 非生产环境下
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  // 生产环境下
  } else {
    updateComponent = () => {
      // 以 vm._render() 函数的返回值作为第一个参数调用 vm._update() 函数
      // vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚拟节点(vnode)
      // vm._update 函数的作用是把 vm._render 函数生成的虚拟节点渲染成真正的 DOM
      vm._update(vm._render(), hydrating)
    }
  }

  // 创建观察者(Watcher)实例，即可称为：渲染函数的观察者
  // watcher 对表达式的求值，触发了数据属性的 get 拦截器函数，从而收集到了依赖，当数据变化时能够触发响应
  // 所以Watcher实例将对 updateComponent 函数求值，updateComponent 函数的执行会间接触发渲染函数(vm.$options.render)的执行
  // 而渲染函数的执行则会触发数据属性的 get 拦截器函数，从而将依赖(观察者)收集
  // 当数据变化时将重新执行 updateComponent 函数，这就完成了重新渲染
  // 创建观察者(Watcher)实例传递的参数分别为：当前组件实例vm, 被观察的目录updateComponent, 回调函数noop, 传递给观察者的选项，
  // 是否在为渲染函数创建观察者对象true 
  new Watcher(vm, updateComponent, noop, {
    before () {
      // 当数据变化之后，触发更新之前，如果vm._isMounted 属性的值为真，则会调用 beforeUpdate 生命周期钩子
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

export function updateChildComponent (
  vm: Component,
  propsData: ?Object,
  listeners: ?Object,
  parentVnode: MountedComponentVNode,
  renderChildren: ?Array<VNode>
) {
  if (process.env.NODE_ENV !== 'production') {
    isUpdatingChildComponent = true
  }

  // determine whether component has slot children
  // we need to do this before overwriting $options._renderChildren
  const hasChildren = !!(
    renderChildren ||               // has new static slots
    vm.$options._renderChildren ||  // has old static slots
    parentVnode.data.scopedSlots || // has new scoped slots
    vm.$scopedSlots !== emptyObject // has old scoped slots
  )

  vm.$options._parentVnode = parentVnode
  vm.$vnode = parentVnode // update vm's placeholder node without re-render

  if (vm._vnode) { // update child tree's parent
    vm._vnode.parent = parentVnode
  }
  vm.$options._renderChildren = renderChildren

  // update $attrs and $listeners hash
  // these are also reactive so they may trigger child update if the child
  // used them during render
  vm.$attrs = parentVnode.data.attrs || emptyObject
  vm.$listeners = listeners || emptyObject

  // update props
  if (propsData && vm.$options.props) {
    toggleObserving(false)
    const props = vm._props
    const propKeys = vm.$options._propKeys || []
    for (let i = 0; i < propKeys.length; i++) {
      const key = propKeys[i]
      const propOptions: any = vm.$options.props // wtf flow?
      props[key] = validateProp(key, propOptions, propsData, vm)
    }
    toggleObserving(true)
    // keep a copy of raw propsData
    vm.$options.propsData = propsData
  }

  // update listeners
  listeners = listeners || emptyObject
  const oldListeners = vm.$options._parentListeners
  vm.$options._parentListeners = listeners
  updateComponentListeners(vm, listeners, oldListeners)

  // resolve slots + force update if has children
  if (hasChildren) {
    vm.$slots = resolveSlots(renderChildren, parentVnode.context)
    vm.$forceUpdate()
  }

  if (process.env.NODE_ENV !== 'production') {
    isUpdatingChildComponent = false
  }
}

function isInInactiveTree (vm) {
  while (vm && (vm = vm.$parent)) {
    if (vm._inactive) return true
  }
  return false
}

export function activateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = false
    if (isInInactiveTree(vm)) {
      return
    }
  } else if (vm._directInactive) {
    return
  }
  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, 'activated')
  }
}

export function deactivateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = true
    if (isInInactiveTree(vm)) {
      return
    }
  }
  if (!vm._inactive) {
    vm._inactive = true
    for (let i = 0; i < vm.$children.length; i++) {
      deactivateChildComponent(vm.$children[i])
    }
    callHook(vm, 'deactivated')
  }
}
// 生命周期钩子函数 hook：要调用的生命周期钩子的名称
// 作用即是：通过 this.$options 访问处理过的对应的生命周期钩子函数数组，遍历并执行它们
export function callHook (vm: Component, hook: string) {
  // #7573 没别的用处，主要是为了避免在某些生命周期钩子中使用 props 数据导致收集冗余的依赖
  pushTarget()
  // 获取要调用的生命周期钩子：即对应的策略函数，生命周期钩子选项最终会被合并处理成一个数组，handlers 就是对应生命周期钩子的数组
  const handlers = vm.$options[hook]
  // 生命周期钩子函数非必须
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      try {
        // handlers[i] 即为生命周期的钩子函数，然后直接通过call直接执行
        // 同时，生命周期的钩子函数体由开发者编写，为捕获其中的异常，故通过try来处理
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }
  // vm._hasHookEvent 是在 initEvents 函数中定义的，其作用是：是否存在生命周期钩子的事件侦听器，初始化值为 false 代表没有，
  // 当组件检测到存在生命周期钩子的事件侦听器时，会将 vm._hasHookEvent 设置为 true(在Vue 事件系统中会体现)
  /**
   * 非官方用法时才执行：代码中加hook:生命周期钩子函数名称，用来监听组件相应的生命周期事件
   * <child
   *  @hook:beforeCreate="handleChildBeforeCreate"
   *  @hook:created="handleChildCreated"
   *  @hook:mounted="handleChildMounted"
   *  @hook:生命周期钩子
   * />
   */
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}
